package de.westranger.forex.trading;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Stack;

import de.westranger.forex.trading.genetic.CSVBasedContentResolver;
import de.westranger.forex.trading.genetic.ContentResolver;
import de.westranger.forex.trading.genetic.allel.ChromosomFactory;
import de.westranger.forex.trading.genetic.allel.Node;
import de.westranger.forex.trading.genetic.allel.chromosom.Chromosom;
import de.westranger.forex.trading.genetic.allel.chromosom.CompositeChromosom;
import de.westranger.forex.trading.genetic.allel.crossover.Crossover;
import de.westranger.forex.trading.genetic.allel.fitness.ComputeFitness;
import de.westranger.forex.trading.genetic.allel.fitness.MoneyManagedFitness;
import de.westranger.forex.trading.genetic.allel.fitness.ParallelComputeCompositeFitness;
import de.westranger.forex.trading.genetic.allel.fitness.ProfitManager;
import de.westranger.forex.trading.genetic.allel.mutation.Mutation;
import de.westranger.forex.trading.genetic.allel.operator.UnknownOperatorException;
import de.westranger.forex.trading.genetic.allel.operator.Util;
import de.westranger.forex.trading.genetic.allel.selection.ChromosomComparator;
import de.westranger.forex.trading.genetic.allel.selection.Selection;

public final class BreederComposite {
	private static final boolean debug = true;
	/**
	 * @param args
	 * @throws ParseException 
	 * @throws IOException 
	 * @throws UnknownOperatorException 
	 */
	public static void main(String[] args){
		try {
			//String file = "G:\\Coding\\ForexTrading\\MT4_vlidation_data.csv.gz";
			//String file = ".\\data\\mql_validation_java_indicator_21.csv.gz";
			//String file = "E:\\EUR_USD_2012\\mql\\mql_validation_java_indicator_12.csv.gz";
			//String file = "E:\\EUR_USD_2012\\indicator_wk1\\mql_validation_java_indicator_1.csv.gz";
			//String file = "E:\\EUR_USD_2013\\indicator\\03_EUR_USD_Week1_indicator.csv";
			//String file = "C:\\tmp\\MT4_validation_MT4_indicator_data_0.csv.gz";
			//String file = "F:\\Program Files (x86)\\OANDA - MetaTrader\\tester\\files\\mql_validation_mql_indicator_0.csv.gz";
			int iter = 8;

			for(int i=1;i<=iter;i++){
				final int id = i;
				String file = ".\\data\\mt4_validation_mql_indicator_"+Integer.toString(id)+".csv.gz";
				
				String prefix = "wk"+Integer.toString(id);
				if(args.length > 0 ){
					if(args[0] != null){
						file = args[0];
					}
					if(args[1] != null){
						prefix = args[1];
					}				 
				}

				evolve(file,prefix);	
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ParseException e) {
			e.printStackTrace();
		} catch (UnknownOperatorException e) {
			e.printStackTrace();
		}
	}

	public static void evolve(final String file,final String prefix) throws IOException, ParseException, UnknownOperatorException {
		final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
		if(debug){
			System.out.println(sdf.format(new Date(System.currentTimeMillis()))+" start ...");
		}
		ProfitManager.LOT = GlobalConfig.OANDA_LOT_SIZE;
		final ContentResolver cr = new CSVBasedContentResolver(file);
		int popsize = GlobalConfig.POPULATION;
		final int maxDepth = GlobalConfig.INITIAL_MAX_DEPTH;
		final int maxGenerationWithoutImprovement = GlobalConfig.GENERATION_NO_IMPROVEMENT; // 10 works fine
		final int generations = GlobalConfig.MAX_GENERATION;
		ParallelComputeCompositeFitness pccf = new ParallelComputeCompositeFitness(GlobalConfig.NUM_THREADS);
		//ComputeFitness ccf = new ComputeCompositeFitness(cr);
		ComputeFitness ccf = new MoneyManagedFitness(cr,GlobalConfig.MONEY,GlobalConfig.MONEY_INVEST,GlobalConfig.LEVERAGE);

		Random rnd = new Random(GlobalConfig.SEED);
		for(int i=0;i<100000;i++){
			rnd.nextDouble();
		}

		final ChromosomFactory chrom = new ChromosomFactory(rnd);
		final Crossover cross = new Crossover(rnd);
		final Selection select = new Selection();
		final Mutation mut = new Mutation(rnd,GlobalConfig.MUTATION_TYPE);

		if(debug){
			System.out.print(sdf.format(new Date(System.currentTimeMillis()))+" generate population");
		}
		Stack<CompositeChromosom> pop = new Stack<CompositeChromosom>();
		int drop = 0;
		for(int indepth=3;indepth<=maxDepth;indepth++){
			for(int i=0;i<popsize/(indepth-1);i++){
				final Node s = chrom.generate(indepth);
				final Node b = chrom.generate(indepth);
				if(Util.checkForInstanceShareing(s, b)){
					throw new IllegalStateException("there somthing wrong with the object genertaion");
				}

				CompositeChromosom cc = new CompositeChromosom(0.0, s,b);
				cc.setTakeProfit(rnd.nextInt(GlobalConfig.TAKE_PROFIT));
				cc.setStoploss(rnd.nextInt(GlobalConfig.STOPLOSS)+1);
//				cc.setTakeProfit(GlobalConfig.TAKE_PROFIT);
//				cc.setStoploss(-GlobalConfig.STOPLOSS);
//				cc.setTradingStartHour(GlobalConfig.TRADING_START_HOUR);
//				cc.setTradingEndHour(GlobalConfig.TRADING_END_HOUR);
				
				final List<Integer> lst = new LinkedList<Integer>();
				for (int j = GlobalConfig.TRADING_START_HOUR; j <= GlobalConfig.TRADING_END_HOUR; j++) {
					lst.add(j);
				}

				cc.setTradingStartHour(lst.remove(rnd.nextInt(lst.size())));
				cc.setTradingEndHour(lst.remove(rnd.nextInt(lst.size())));
				if(cc.getTradingEndHour() < cc.getTradingStartHour()){
					final int swap = cc.getTradingEndHour();
					cc.setTradingEndHour(cc.getTradingStartHour());
					cc.setTradingStartHour(swap);
				}
				
//				System.out.println(cc.getTradingStartHour() + "\t" + cc.getTradingEndHour() + "\t" + cc.toString());
				
				pop.push(cc);
			}
		}

		if(pop.size() % 2 != 0){
			final Node s = chrom.generate(maxDepth);
			final Node b = chrom.generate(maxDepth);
			CompositeChromosom cc = new CompositeChromosom(0.0, s,b);
			cc.setTakeProfit(rnd.nextDouble()*GlobalConfig.TAKE_PROFIT);
			cc.setStoploss(rnd.nextDouble()*GlobalConfig.STOPLOSS);
//			cc.setTakeProfit(GlobalConfig.TAKE_PROFIT);
//			cc.setStoploss(GlobalConfig.STOPLOSS);
//			cc.setTradingStartHour(GlobalConfig.TRADING_START_HOUR);
//			cc.setTradingEndHour(GlobalConfig.TRADING_END_HOUR);
			
			final List<Integer> lst = new LinkedList<Integer>();
			for (int i = GlobalConfig.TRADING_START_HOUR; i <= GlobalConfig.TRADING_END_HOUR; i++) {
				lst.add(i);
			}

			cc.setTradingStartHour(lst.remove(rnd.nextInt(lst.size())));
			cc.setTradingEndHour(lst.remove(rnd.nextInt(lst.size())));
			if(cc.getTradingEndHour() < cc.getTradingStartHour()){
				final int swap = cc.getTradingEndHour();
				cc.setTradingEndHour(cc.getTradingStartHour());
				cc.setTradingStartHour(swap);
			}

//			System.out.println(cc.getTradingStartHour() + "\t" + cc.getTradingEndHour() + "\t" + cc.toString());
			
			pop.push(cc);
		}
		if(debug){
			System.out.println(" ... done (generated "+pop.size()+" dropped "+drop+")");
		}

		Chromosom[] population = new CompositeChromosom[pop.size()];
		Chromosom[] populationNew = new CompositeChromosom[pop.size()];
		popsize = pop.size();
		
		pop.copyInto(population);
		pop.clear();
		if(debug){
			System.out.print(sdf.format(new Date(System.currentTimeMillis()))+" compute fitness ... ");
		}
		pccf.produce((CompositeChromosom[]) population, cr);
		if(debug){
			System.out.println(sdf.format(new Date(System.currentTimeMillis()))+" done");
		}
		
		if(debug){
			System.out.println(sdf.format(new Date(System.currentTimeMillis()))+" evolve\n");
			System.out.println("date;generation;sum;min;max;avg;profit;remate;remate_percent;mutation_count;mutation_rate");
		}
			
		int reMate = 0;
		Chromosom maxChrom = null;
		Chromosom omniChrom = null;
		
		int noImprovement=0;
		
		for(int g=0;g<generations;g++){
			noImprovement++;
			if(debug){
				System.out.print(sdf.format(new Date(System.currentTimeMillis()))+";"+g+";");
			}
			double sum = 0.0;
			double min = Double.POSITIVE_INFINITY;
			double max = Double.NEGATIVE_INFINITY;
			for(Chromosom i:population){
				sum += i.getFitness();
				min = Math.min(min, i.getFitness());
				max = Math.max(max, i.getFitness());
				
				if(i.getFitness() == max){
					maxChrom = i;
				}
			}
			if(debug){
				System.out.print(sum + ";" + min + ";" +max + ";" + (sum/(double)popsize)+";" + ccf.computeFitness(maxChrom).getProfit());
			}
				
			population = select.selectStockasticUniversalSampling(population);
			
			mut.setTime(g);
			
			for(int i=0;i<popsize;i+=2){
				final Chromosom[] children = cross.mate(population[i],population[i+1]);

				if(children == null){

					reMate++;
					i-=2;
				}else{
					mut.mutate(children[0]);
					populationNew[i] = children[0];
					
					mut.mutate(children[1]);
					populationNew[i+1] = children[1];
				}
			}
			if(debug){
				System.out.print(";cf");
			}
			pccf.produce((CompositeChromosom[]) populationNew, cr);
			
			if(debug){
				System.out.println(";"+reMate+";"+((double)reMate/(double)popsize)+";"+mut.getMutationCount()+";"+mut.getMutationRate());
			}

			if(omniChrom == null){
				omniChrom = maxChrom;
				noImprovement = 0;
			}else if(ccf.computeFitness(omniChrom).getProfit() < ccf.computeFitness(maxChrom).getProfit() || g == 1){
				omniChrom = maxChrom;
				if(debug){
					if(prefix.length() < 1){
						writeToFile((CompositeChromosom) omniChrom,g,"");
					}else{
						writeToFile((CompositeChromosom) omniChrom,g,prefix);					
					}
				}
				noImprovement = 0;
			}
			
			if(noImprovement >= maxGenerationWithoutImprovement){
				break;
			}
			
			reMate=0;
			Arrays.sort(populationNew, new ChromosomComparator());
			population = populationNew;
			Util.normalize(population);
			populationNew = new CompositeChromosom[popsize];
			mut.resetMutationCount();
		}
	
		System.out.println(ccf.computeFitness(omniChrom).getProfit());
	}
	
	
	public static void writeToFile(final CompositeChromosom omni, final int gen,final String prefix){
		try {
			FileOutputStream fos = new FileOutputStream("./out/"+prefix+"_"+gen+"_result.ser");
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject((CompositeChromosom)omni);
			fos.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}		
	}	
}
